package com.gframework.mybatis.util.generator.core.util;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类扫描器，可以扫描指定包下，指定接口的子类，包括本地和jar包中的.
 * <p>如果一个包即在本地存在，也在jar包存在，那么只会读取本地的。
 * @since 1.0.0
 * @author Ghwolf
 *
 */
public class ClassScanner {
	/**
	 * 扫描指定包下(包括子包)所有的类.<br>
	 * 
	 * @param packageName 包名称
	 * @return 返回扫描出来的类集合，如果没有集合长度为0
	 */
	public static List<Class<Object>> scannerPackage(String packageName) {
		return scannerPackage(packageName,null);
	}
	
	/**
	 * 扫描指定包下(包括子包)所有特定接口的子类.<br>
	 * 
	 * @param packageName 包名称
	 * @param interfaceClass 接口类型
	 * @return 返回扫描出来的类集合，如果没有集合长度为0
	 */
	public static <T,R extends T> List<Class<R>> scannerPackage(String packageName,Class<T> interfaceClass) {
		URL url = ClassLoader.getSystemClassLoader().getResource(packageName.replace(".", "/"));
		String protocol = url.getProtocol();
		if ("file".equals(protocol)) {
			// 本地代码
			return scannerLocal(packageName,interfaceClass);
		} else if ("jar".equals(protocol)) {
			// jar包代码
			return scannerJar(packageName,interfaceClass);
		} else {
			return Collections.emptyList();
		}
	}
	
	
	/**
	 * 扫描本地包
	 */
	private static <T,R extends T> List<Class<R>> scannerLocal(String packName,Class<T> interfaceClass) {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		URI url = null;
		try {
			url = classLoader.getResource(packName.replace(".", "/")).toURI();
		} catch (URISyntaxException e1) {
			throw new RuntimeException("未找到资源：" + packName);
		}

		File file = new File(url);
		List<Class<R>> classes = new ArrayList<>();
		file.listFiles(new FileFilter() {

			@SuppressWarnings("unchecked")
			public boolean accept(File chiFile) {
				if (chiFile.isDirectory()) {
					List<Class<R>> clses = scannerLocal(packName + "." + chiFile.getName(),interfaceClass);
					classes.addAll(clses);
				}
				String name = chiFile.getName();
				if ("package-info.class".equals(name) || "module-info.class".equals(name)) {
					return false;
				}
				if (name.endsWith(".class")) {
					Class<R> clazz = null;
					try {
						String className = packName + "." + name.substring(0, name.length() - ".class".length());
						clazz = (Class<R>) Class.forName(className,false,classLoader);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
					if (interfaceClass == null) {
						classes.add(clazz);
					} else {
						if (interfaceClass.isAssignableFrom(clazz)) {
							classes.add(clazz);
						}
					}
					return true;
				}
				return false;
			}
		});
		
		return classes;
	}
	/**
	 * 扫描jar包
	 */
	@SuppressWarnings("unchecked")
	private static <T,R extends T> List<Class<R>> scannerJar(String packName,Class<T> interfaceClass) {
		ClassLoader classLoader = ClassLoader.getSystemClassLoader();
		String pathName = packName.replace(".", "/");
		JarFile jarFile = null;
		try {
			URL url = classLoader.getResource(pathName);
			JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
			jarFile = jarURLConnection.getJarFile();
		} catch (IOException e) {
			throw new RuntimeException("未找到资源：" + packName);
		}

		List<Class<R>> classes = new ArrayList<>();
		Enumeration<JarEntry> jarEntries = jarFile.entries();
		while (jarEntries.hasMoreElements()) {
			JarEntry jarEntry = jarEntries.nextElement();
			String jarEntryName = jarEntry.getName();

			if (jarEntryName.contains(pathName) && !jarEntryName.equals(pathName + "/")) {
				// 递归遍历子目录
				if (jarEntry.isDirectory()) {
					String clazzName = jarEntry.getName().replace("/", ".");
					int endIndex = clazzName.lastIndexOf(".");
					String prefix = null;
					if (endIndex > 0) {
						prefix = clazzName.substring(0, endIndex);
					}
					classes.addAll(scannerJar(prefix,interfaceClass));
				}
				String name = jarEntry.getName();
				if ("package-info.class".equals(name) || "module-info.class".equals(name)) {
					continue ;
				}
				if (name.endsWith(".class")) {
					Class<R> clazz = null;
					try {
						String className = name.replace("/", ".").substring(0,name.length() - ".class".length());
						clazz = (Class<R>) Class.forName(className, false, classLoader);
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
					if (interfaceClass == null) {
						classes.add(clazz);
					} else {
						if (interfaceClass.isAssignableFrom(clazz)) {
							classes.add(clazz);
						}
					}
				}
			}
		}
		return classes;
	}
}
